package com.zhoukai.manage.util;

import cn.hutool.core.codec.Base64;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class EncryptUtil {

    private final static String RSA = "RSA";

    /**
     * 密钥长度，DH算法的默认密钥长度是1024
     * 密钥长度必须是64的倍数，在512到65536位之间
     */
    private static final int KEY_SIZE = 1024;

    //公钥
    public static final String PUBLIC_KEY_STRING = "publicKey";

    //私钥
    public static final String PRIVATE_KEY_STRING = "privateKey";


    public static Map<String, String> initGenerateKeyPair() throws NoSuchAlgorithmException {

        //  创建密钥对生成器对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE);

        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate(); // 私钥
        PublicKey publicKey = keyPair.getPublic(); // 公钥

        //将密钥存对储在map中
        Map<String, String> keyMap = new HashMap<String, String>();
        keyMap.put(PUBLIC_KEY_STRING, Base64.encode(publicKey.getEncoded()));
        keyMap.put(PRIVATE_KEY_STRING, Base64.encode(privateKey.getEncoded()));
        return keyMap;
    }

    /**
     * 私钥加密
     *
     * @param data             待加密数据
     * @param privateKeyString 密钥
     * @return String 加密数据
     */
    public static String encryptByPrivateKey(String data, String privateKeyString) throws Exception {
        //取得私钥
        PrivateKey privateKey = loadPrivateKeyFromString(privateKeyString);
        // 加密
        return encryptByPrivateKey(data, privateKey);
    }

    /**
     * 公钥加密
     *
     * @param data            待加密数据
     * @param publicKeyString 密钥
     * @return String 加密数据
     */
    public static String encryptByPublicKey(String data, String publicKeyString) throws Exception {
        //获取公钥
        PublicKey pubKey = loadPublicKeyFromString(publicKeyString);
        // 加密
        return encryptByPrivateKey(data, pubKey);
    }

    /**
     * 私钥解密
     *
     * @param data             待解密数据
     * @param privateKeyString 密钥
     * @return byte[] 解密数据
     */
    public static String decryptByPrivateKey(String data, String privateKeyString) throws Exception {
        //取得私钥
        PrivateKey privateKey = loadPrivateKeyFromString(privateKeyString);
        //数据解密
        return decryptByAsymmetric(data, privateKey);
    }

    /**
     * 公钥解密
     *
     * @param data            待解密数据
     * @param publicKeyString 密钥
     * @return byte[] 解密数据
     */
    public static String decryptByPublicKey(String data, String publicKeyString) throws Exception {

        //取得公钥
        PublicKey publicKey = loadPublicKeyFromString(publicKeyString);
        //数据解密
        return decryptByAsymmetric(data, publicKey);
    }

    /**
     * 从字符串中加载公钥
     *
     * @param publicKeyString : 公钥对字符串
     * @return : 公钥
     * @throws Exception
     */
    public static PublicKey loadPublicKeyFromString(String publicKeyString) {
        try {
            // 进行Base64解码
            byte[] decode = Base64.decode(publicKeyString);
            // 获取密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 构建密钥规范
            X509EncodedKeySpec key = new X509EncodedKeySpec(decode);
            // 获取公钥
            return keyFactory.generatePublic(key);
        } catch (Exception e) {
            log.error("获取公钥失败！", e);
            throw new RuntimeException("获取公钥失败！");
        }
    }

    /**
     * 从字符串中加载私钥
     *
     * @param keyString : 私钥对字符串
     * @return : 私钥
     * @throws Exception
     */
    public static PrivateKey loadPrivateKeyFromString(String keyString) {
        try {
            // 进行Base64解码
            byte[] decode = Base64.decode(keyString);
            // 获取密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 构建密钥规范
            PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(decode);
            // 生成私钥
            return keyFactory.generatePrivate(key);
        } catch (Exception e) {
            log.error("获取私钥失败！", e);
            throw new RuntimeException("获取私钥失败！");
        }
    }

    /**
     * 非对称加密数据
     *
     * @param input : 原文
     * @param key   : 密钥
     * @return : 密文
     * @throws Exception
     */
    public static String encryptByPrivateKey(String input, Key key) {
        try {
            // 获取Cipher对象
            Cipher cipher = Cipher.getInstance(RSA);
            // 初始化模式(加密)和密钥
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] resultBytes = getMaxResultEncrypt(input, cipher);
            return Base64.encode(resultBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加密失败！");
        }
    }

    /**
     * 非对称解密数据
     *
     * @param encrypted : 密文
     * @param key       : 密钥
     * @return : 原文
     * @throws Exception
     */
    public static String decryptByAsymmetric(String encrypted, Key key) {
        try {
            // 获取Cipher对象
            Cipher cipher = Cipher.getInstance(RSA);
            // 初始化模式(解密)和密钥
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(getMaxResultDecrypt(encrypted, cipher));
        } catch (
                Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解密失败！");
        }
    }

    /**
     * 分段处理加密数据
     *
     * @param input  : 加密文本
     * @param cipher : Cipher对象
     * @return
     */
    private static byte[] getMaxResultEncrypt(String input, Cipher cipher) throws Exception {
        byte[] inputArray = input.getBytes();
        int inputLength = inputArray.length;
        // 最大加密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 117;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 分段处理解密数据
     *
     * @param decryptText : 加密文本
     * @param cipher      : Cipher对象
     * @throws Exception
     */
    private static byte[] getMaxResultDecrypt(String decryptText, Cipher cipher) throws Exception {
        byte[] inputArray = Base64.decode(decryptText.getBytes(StandardCharsets.UTF_8));
        int inputLength = inputArray.length;

        // 最大解密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 128;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

//    public static void main(String[] args) throws Exception {
//        //生成密钥对
//        Map<String, String> keyMap = initGenerateKeyPair();
//
//        //公钥
//        String publicKeyString = keyMap.get(PUBLIC_KEY_STRING);
//        String privateKeyString = keyMap.get(PRIVATE_KEY_STRING);
//
//        System.out.println("公钥：" + publicKeyString);
//        System.out.println("私钥：" + privateKeyString);
//
//        System.out.println("================密钥对构造完毕,甲方将公钥公布给乙方，开始进行加密数据的传输=============");
//        String str = "aattaggcctegthththfefqwq asdasd求安慰";
//        System.out.println("===========甲方向乙方发送加密数据==============");
//        System.out.println("原文:" + str);
//        //甲方进行数据的加密
//        String code1 = encryptByPublicKey(str, publicKeyString);
//        System.out.println("甲方 使用乙方公钥加密后的数据：" + code1);
//        System.out.println("===========乙方使用甲方提供的公钥对数据进行解密==============");
//
//        //乙方进行数据的解密
//        String decode1 = decryptByPrivateKey(code1, privateKeyString);
//        System.out.println("乙方解密后的数据：" + decode1 + "");
//    }
}

